package com.airS.task.fileMgr.common.aspect;

import com.airS.task.fileMgr.common.anno.Invoke;
import com.airS.task.fileMgr.mapper.sys.SysOperalogMapper;
import com.airS.task.fileMgr.model.sys.SysOperalog;
import com.airS.task.fileMgr.security.shiro.realm.SystemAuthorizingRealm;
import com.airS.task.fileMgr.utils.ip.LatnUtil;
import com.airS.task.fileMgr.utils.servlet.ServletUtils;
import com.airS.task.fileMgr.utils.shiro.SecurityUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.sun.org.apache.xpath.internal.SourceTree;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Created by alan on 2017/03/17.
 */
@Aspect
@Component
public class InvokeLogAspect {

    private static Logger logger = LoggerFactory.getLogger(InvokeLogAspect.class);

    @Autowired
    private SysOperalogMapper sysOperalogMapper;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource(name = "redisTemplateForLog")
    private RedisTemplate redisTemplate;

    @Around("@annotation(invoke)")
    protected Object executeAround(ProceedingJoinPoint joinPoint,final Invoke invoke) throws Throwable{
        Object returnValue = null; // 目标方法返回值
        String returnMessage = null;
        final SysOperalog sysOperalog = new SysOperalog();
        SystemAuthorizingRealm.Principal principal = SecurityUtils.getPrincipal();
        if(principal != null){
            String empName = SecurityUtils.getPrincipal().getEmployeeName();
            Integer empId = SecurityUtils.getPrincipal().getEmployeeId();
            sysOperalog.setEmployeeName(String.valueOf(empName));
            sysOperalog.setEmployeeId(empId == null ? 0 : empId);
        }
        sysOperalog.setCreateTime(new Date());
        HttpServletRequest request = ServletUtils.getRequest();
        if(request != null){
            sysOperalog.setClientIp(LatnUtil.getIpAddr(request) + ":" + LatnUtil.getPort(request)); // 客户端IP
        }
        sysOperalog.setServerIp(LatnUtil.getLocalIPList().toString()); // 服务端IP

        Runtime sRuntime = Runtime.getRuntime();

        sysOperalog.setStartMem(sRuntime.freeMemory() / 1024 / 1024 + "M"); // 开始内存
        sysOperalog.setStartTotal(sRuntime.totalMemory() / 1024 / 1024 + "M"); // 开始总内存
        sysOperalog.setLogType(invoke.logType().logType); // 日志类型
        sysOperalog.setLogTypeName(invoke.logType().logTypeName); // 日志类型名称


        MethodSignature methodSignature = (MethodSignature) joinPoint
                .getSignature();
        Method method = methodSignature.getMethod();
        String[] parameterNames = methodSignature.getParameterNames();

        Class<?>[] parameterTypes = method.getParameterTypes();


        StringBuilder parameter = new StringBuilder();
        parameter.append("调用方法：");
        parameter.append(joinPoint.getTarget().getClass().getName())
                .append(".").append(joinPoint.getSignature().getName()).append(
                "(");
        for (int i = 0; i < parameterTypes.length; i++) { // 记录目标方法形参列表
            parameter.append(parameterTypes[i].getName()).append(" ").append(parameterNames[i]).append(",");
        }
        parameter.deleteCharAt(parameter.length() - 1);
        parameter.append(")");
        parameter.append("\r\n传递参数：");

        // 获取目标方法传入参数值
        Object[] args = joinPoint.getArgs();

        if (args != null && args.length != 0) {
            parameter.append("(");
            for (int i = 0; i < args.length; i++) {
                parameter.append(args[i] == null ? "null" : assemblyJavaBean(args[i]))
                        .append(",");
            }
            parameter.deleteCharAt(parameter.length() - 1);
            parameter.append(")");
        } else {
            parameter.append("none");
        }

        // 计算方法执行时间
        Long startTime = System.currentTimeMillis();

        try {
            returnValue = joinPoint.proceed(); // 执行目标方法
            sysOperalog.setOperaStatus("操作成功"); // 操作状态
        }catch (Throwable e) {
            returnMessage = "\r\n异常类型：" + e.getClass().getName() + "\r\n异常消息："
                    + e.getMessage() + "\r\n异常堆栈：" + JSONArray.toJSONString(e.getStackTrace());
            sysOperalog.setOperaStatus("操作失败");
            throw e;
        } finally {
            Long endTime = System.currentTimeMillis();
            if (null != returnMessage) {
                parameter.append(returnMessage);
            }
            parameter.append( "\r\n返回值："+ (returnValue == null ? "void" : returnValue.getClass().isArray() ? JSONArray.toJSONString(returnValue) : JSONObject.toJSONString(returnValue)));

            // 设置日志的详细信息
            sysOperalog.setOperaDetail(StringUtils.substring(parameter.toString(),0,4000));
            // 设置方法的调用时间
            sysOperalog.setInvokeTime(new Long(endTime-startTime).intValue());

            Runtime eRuntime = Runtime.getRuntime();
            sysOperalog.setEndMem(eRuntime.freeMemory() / 1024 / 1024 + "M"); // 结束内存
            sysOperalog.setEndTotal(eRuntime.totalMemory() / 1024 / 1024 + "M"); // 结束总内存

            if (endTime-startTime > -1){
                try{
                    // 异步写入DB
                    threadPoolTaskExecutor.execute(new Runnable() {
                        @Override
                        public void run() {
                            try{
                                // 写入DB
                                int retId = sysOperalogMapper.insert(sysOperalog);
                                boolean f = retId > 0 ? true : false;
                                logger.info("系统日志保存" + ( f ? "成功" : "失败") + "," + invoke.logType().logTypeName);

                                // 写入redis缓存，用于统一日志技术方案展示
//                                sysOperalog.setOperaId(retId);
//                                ListOperations<String, String> operations = redisTemplate.opsForList();
//                                String log = JSON.toJSONString(sysOperalog);
//                                operations.leftPush("logstash-list", log); // 左边进，右边出
                            }catch(Exception e){
                                logger.error("系统日志写入DB异常，请检查",e);
                            }
                        }
                    });
                }catch(Exception e){
                    logger.error("系统日志写入DB异常，请检查",e);
                }
            }
        }
        return returnValue;
    }

    private String assemblyJavaBean(Object obj){
        try {
            if (obj == null) {
                return "null";
            }
            //基本类型及字符
            if (obj instanceof String || obj.getClass().isPrimitive()) {
                return String.valueOf(obj);
            }
            //数组
            if (obj.getClass().isArray()) {
                return JSONArray.toJSONString(obj);
            }
            if (obj instanceof ServletRequest || obj instanceof HttpServletRequest || obj instanceof ServletResponse || obj instanceof HttpServletResponse){
                return String.valueOf(obj);
            }
            return JSON.toJSONString(obj);
        }catch(Throwable e){
            logger.warn("parse error : " + e);
            return String.valueOf(obj);
        }
    }
}
